﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.ServiceProcess;
using System.Text;
using System.Timers;
using Common;

namespace Production_service
{
    public partial class Service1 : ServiceBase
    {
        private static readonly object lockobject = new object();
        private Timer _timer;

        #region Consts
        private const String SERVICE_NAME = "PresbetonProductionService";
        private const String THREAD_NAME = "Presbeton production service";
        private const String SERVER_IP = "127.0.0.1";
        #endregion Consts

        static UdpClient udp;
        static IPEndPoint ipe;

        static int ReceivePort;
        static Timer MyTimer = new Timer();
        static int TimerInterval = 240000;

        public Service1()
        {
            InitializeComponent();

            this.CanShutdown = true;
            this.CanStop = true;
        }

        static void Main(string[] args)
        {
            System.ServiceProcess.ServiceBase[] ServicesToRun;
            //Change the following line to match. 
            ServicesToRun = new System.ServiceProcess.ServiceBase[] { new Service1() };
            System.ServiceProcess.ServiceBase.Run(ServicesToRun);
        }

        protected override void OnStart(string[] args)
        {
            EventLogManager.WriteToLog(String.Format("Config: {0}, {1}", Configuration.DBName, Configuration.DBServer));

            if (DataOperate.OpenData(Configuration.DBServer, Configuration.DBName))
            {
                DataOperate.WriteToDbLog("Log", "Starting service");
                DataOperate.WriteToDbLog("Log", String.Format("Config: {0}, {1}", Configuration.DBName, Configuration.DBServer));
                ReceivePort = (Int32)Configuration.ReceivePort; // Int32.Parse(DataOperate.GetParameter("ReceivePort"));

                DataOperate.WriteToDbLog("Log", String.Format("Receive port [{0}]", ReceivePort));

                #region "Initialize udp component and callback method"
                try
                {
                    ipe = new IPEndPoint(IPAddress.Any, ReceivePort);
                }
                catch (Exception exp)
                {
                    DataOperate.WriteToDbLog("Exp", exp.Message);
                }

                DataOperate.WriteToDbLog("Log", "ipe created");

                MyTimer.Elapsed += new ElapsedEventHandler(OnTimer);
                MyTimer.AutoReset = false;
                MyTimer.Interval = TimerInterval;
                MyTimer.Start();

                udp = new UdpClient(ipe);
                udp.Client.ReceiveBufferSize = 8192;
                udp.Client.SendBufferSize = 8192;

                DataOperate.WriteToDbLog("Log", "udp created");

                IAsyncResult ia = udp.BeginReceive(ReceiveCallback, udp);
                DataOperate.WriteToDbLog("Log", "udp begin receive");
                #endregion
            }
        }

        protected override void OnStop()
        {
            if (udp != null)
            {
                udp.Close();
            }

            DataOperate.WriteToDbLog("Log", "Stopping service");

            base.OnStop();
        }

        protected override void OnShutdown()
        {
            base.OnShutdown();
        }

        // my own procedures
        private void ReceiveCallback(IAsyncResult ar)
        {
            byte[] recvBytes = udp.EndReceive(ar, ref ipe);
            MyTimer.Stop();

            ProcessData(recvBytes);

            if (ar.IsCompleted)
            {
                ar = udp.BeginReceive(ReceiveCallback, udp);

                MyTimer.Interval = TimerInterval;
                MyTimer.Start();
            }
        }

        private void ProcessData(byte[] ReceiveBytes)
        {
            DataOperate.WriteToDbLog("Log", String.Format("Process data [{0} bytes]", ReceiveBytes.Length));
            SendResponse(ReceiveBytes[1], ReceiveBytes[2], ReceiveBytes[3]);

            if (CheckSum(ReceiveBytes))
            {
                int[] Values = new int[38];
                int packetNumber = 0;
                string recipeIdentifier = String.Empty;

                if (ReceiveBytes.Length == 98)
                {
                    packetNumber = BytesToInt(ReceiveBytes[2], ReceiveBytes[3]);

                    byte[] recipeName = new byte[16];
                    Buffer.BlockCopy(ReceiveBytes, 4, recipeName, 0, 16);
                    recipeIdentifier = Encoding.ASCII.GetString(recipeName);

                    for (int i = 0; i < 38; i++)
                    {
                        int idx = 20 + 2 * i;

                        try
                        {
                            Values[i] = BytesToInt(ReceiveBytes[idx], ReceiveBytes[idx+1]);
                        }
                        catch (Exception ex)
                        {
                            Values[i] = 0;
                            DataOperate.WriteToDbLog("Exp", ex.Message);
                        }
                    }

                    DataOperate.WriteToDbLog("Log", "Send response");
                    SendResponse(ReceiveBytes[1], ReceiveBytes[2], ReceiveBytes[3]);
                }

                DataOperate.WriteRecipeSilo(recipeIdentifier);
                DataOperate.WriteValues(Convert.ToChar(ReceiveBytes[1]), packetNumber, recipeIdentifier, Values);
                String debugData = String.Empty;

                foreach (byte Data in ReceiveBytes) debugData += debugData != String.Empty ? "-" + Data.ToString() : Data.ToString();
                DataOperate.WriteToDbLog("Log", debugData);
            }
            else
            {
                DataOperate.WriteToDbLog("Log", "CheckSum - invalid");
            }
        }

        private Boolean CheckSum(byte[] ReceiveBytes)
        {
            int Sum = 0;

            foreach (byte Data in ReceiveBytes)
            {
                Sum = Sum ^ Data;
            }

            return (Sum == 0);
        }

        public void OnTimer(Object source, ElapsedEventArgs e)
        {
            MyTimer.Stop();

            // timeout - reconnect udp !!!
            DataOperate.WriteToDbLog("Msg", "Timer - reconnect UDP");

            try
            {
                IAsyncResult ia = udp.BeginReceive(ReceiveCallback, udp);
            }
            catch (Exception ex)
            {
                DataOperate.WriteToDbLog("Exp", ex.Message);
            }

            MyTimer.Interval = TimerInterval;
            MyTimer.Start();
        }

        static private void IntToBytes(int InputValue, ref byte B1, ref byte B2)
        {
            B1 = (byte)(InputValue / 256);
            B2 = (byte)(InputValue % 256);
        }

        static private int BytesToInt(byte B1, byte B2)
        {
            return B1 * 256 + B2;
        }

        private void PutCheckSum(int Offset, byte[] Buf)
        {
            int chs = 0;

            for (int i = Offset; i < Buf.Length - 1; i++)
            {
                chs = chs ^ Buf[i];
            }

            Buf[Buf.Length - 1] = Convert.ToByte(chs);
        }

        private void SendResponse(byte rb1, byte rb2, byte rb3)
        {
            byte[] sendData = new byte[18];

            sendData[0] = 0x02;
            
            sendData[1] = rb1;
            sendData[2] = rb2;
            sendData[3] = rb3;

            sendData[16] = 0x03;

            PutCheckSum(0, sendData);

            udp.Connect(ipe);
            udp.Send(sendData, sendData.Length);
        }
    }
}
